package domein;

import java.net.*;
import java.util.*;
import java.util.logging.Logger;

import command.*;

/**
 * 
 * @author rensbonnez
 * Every peer implements this class. Here we store all the information
 * about the peer.
 *
 */

public class PeerServer extends Thread 
{
	public static Logger logger = Logger.getLogger("MyLog");
	private PeerController peerController;
	private DatagramPacketQueu datagramPacketQueu;
	private InetAddress multicastGroup;
	private Thread receivingThread;
	private boolean serverIsRunning;
	private static final int PORT = 8501;
	
	public PeerServer(PeerController peerController)
	{
		datagramPacketQueu = new DatagramPacketQueu(2);
		setPeerController(peerController);
		setServerIsRunning(true);
	}
	
	public void startServer()
	{
		try 
		{
			//Setup shared socket
			setMulticastGroup(InetAddress.getByName("230.0.0.1"));
			getSocket().joinGroup(getMulticastGroup());
		} 
		catch (BindException e)
		{
			logger.info("Ip-Address already in use. Restart program or contact your administrator.");
			setServerIsRunning(false);
		}
		catch (Exception e)
		{
			e.printStackTrace();
			setServerIsRunning(false);
		}
		
		//Receive incomming packets with Thread and store in the Queu
		receivingThread = new Thread()
		{
			@Override
			public void run()
			{				
				while(isServerIsRunning())
				{
					try 
					{
						int packetSize = Settings.getMaxFileSize();
						DatagramPacket receivedPacket = new DatagramPacket(new byte[packetSize], packetSize);
						getSocket().receive(receivedPacket);
						datagramPacketQueu.addPacket(receivedPacket);
					} 
					catch (SocketException e)
					{
						logger.info("Can't receive messages, socket is closed.");
						setServerIsRunning(false);
					}
					catch (Exception e) 
					{
						e.printStackTrace();
						setServerIsRunning(false);
					}
				}
			}
		};
		
		receivingThread.start();
		start();
	}
	
	public void stopServer()
	{
		setServerIsRunning(false);
		try 
		{
			sendMessage(new Command(CommandType.GOODBYE), getMulticastGroup());
			getSocket().leaveGroup(getMulticastGroup());
			getSocket().close();
			logger.info("\nLocalhost left group.");
		} 
		catch (SocketException e)
		{
			logger.info("Can't send messages, socket is closed.");
		}
		catch (Exception e) 
		{
			e.printStackTrace();
		}
	}

	/**
	 * Thread for handling packets.
	 */
	@Override
	public void run()
	{	
		//Start shutdown thread
		registerShutdown();
		
		//Send HELLO broadcast to the network
		sendMessage(new Command(CommandType.HELLO), getMulticastGroup());
		logger.info("\nBroadcast send to join network.");
		
		while(isServerIsRunning())
		{
			DatagramPacket receivedPacket = datagramPacketQueu.getPacket();
			
			if(!peerController.getLocalHost().getIpAddress().equals(receivedPacket.getAddress()))
			{
				String incoming = new String(receivedPacket.getData(), 0 , receivedPacket.getLength());
				Command command = new Command(incoming);
				
				logger.info("=============NEW PACKET RECEIVED=============\n" +  incoming);

				if(!peerController.getPeers().containsKey(receivedPacket.getAddress()))
				{
					peerController.addPeer(new Peer(receivedPacket.getAddress(), receivedPacket.getAddress().getHostName()));
					logger.info("New connection accepted.");
				}
				
				//Another peer joined.
				if(command.getCommandType() == CommandType.HELLO)
				{
					sendMessage(new Command(CommandType.WELCOME), receivedPacket.getAddress());
					sendMessage(new Command(CommandType.GETLIST), receivedPacket.getAddress());
				}
				//Localhost just joined, will receive Welcome messages of all active peers
				else if(command.getCommandType() == CommandType.WELCOME)
				{
					sendMessage(new Command(CommandType.GETLIST), receivedPacket.getAddress());
				}
				//A peer wants files from the localhost
				else if(command.getCommandType() == CommandType.GETLIST)
				{
					sendMessage(new CommandSendList(CommandType.SENDLIST, peerController.getLocalHost().getFilesAsString()), receivedPacket.getAddress());
				}
				//Localhost wants files from a peer.
				else if(command.getCommandType() == CommandType.SENDLIST)
				{
					if(command.getMessage() != null)
					{
						List<PeerFile> tempPeerFiles = new ArrayList<PeerFile>();
						
						for (String sharedDir : command.getMessage().split("<FS>")) 
						{
							String[] params = sharedDir.split("<PS>"); 

							try {
								PeerFile peerFile = new PeerFile(Integer.parseInt(params[0]), params[1], Long.parseLong(params[2]), params[3]);
								tempPeerFiles.add(peerFile);
							} catch (Exception e) {
								e.printStackTrace();
							}
						}
						
						Peer peer = peerController.getPeer(receivedPacket.getAddress());
						peer.setPeerFiles(tempPeerFiles);
						peerController.notifyObservers();
						logger.info(tempPeerFiles.size() + " files received from peer " + receivedPacket.getAddress());
					}
					else
					{
						logger.info("No files received from peer " + receivedPacket.getAddress());
					}
				}
				//A peer left the network
				else if(command.getCommandType() == CommandType.GOODBYE)
				{
					peerController.removePeer(peerController.getPeer(receivedPacket.getAddress()));
				}
			}
		}
	}
	
	private void registerShutdown()
	{
		Runtime.getRuntime().addShutdownHook(new Thread()
		{
			@Override
			public void run()
			{
				try 
				{
					sendMessage(new Command(CommandType.GOODBYE), getMulticastGroup());
					getSocket().leaveGroup(getMulticastGroup());
					getSocket().close();
					logger.info("Localhost left group.");
					setServerIsRunning(false);
				}
				catch (SocketException e)
				{
					logger.info("Can't send messages, socket is closed.");
				}
				catch (Exception e) 
				{
					e.printStackTrace();
					setServerIsRunning(false);
				}
			}
		});
	}
	
	public void localHostUpdatedFiles()
	{
		sendMessage(new CommandSendList(CommandType.SENDLIST, peerController.getLocalHost().getFilesAsString()), getMulticastGroup());
	}
	
	public void sendMessage(Command command, InetAddress inetAddress) 
	{
		try 
		{	
			String commandString = command.toString();
			byte[] buffer = commandString.getBytes();
			DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length, inetAddress, PORT);
			SharedSocket.sendPacket(datagramPacket);
			
			logger.info("=============NEW PACKET SENDED=============\n" + commandString + "-packet sended to " + inetAddress);
		}
		catch (SocketException e)
		{
			logger.info("Can't send/receive messages, socket is closed.");
		}
		catch (Exception e) 
		{	
			e.printStackTrace();
		}
	}
	
	public MulticastSocket getSocket()
	{
		return SharedSocket.getInstance();
	}
	
	/**
	 * Getters and Setters
	 */

	public PeerController getPeerController() {
		return peerController;
	}

	public void setPeerController(PeerController peerController) {
		this.peerController = peerController;
	}

	public boolean isServerIsRunning() {
		return serverIsRunning;
	}

	public void setServerIsRunning(boolean serverIsRunning) {
		this.serverIsRunning = serverIsRunning;
	}
	
	public InetAddress getMulticastGroup() {
		return multicastGroup;
	}

	public void setMulticastGroup(InetAddress multicastGroup) {
		this.multicastGroup = multicastGroup;
	}
}
